#include "cameradecklink.h"

#include <opencv/cv.h>
#include <opencv/highgui.h>

CameraDecklink::CameraDecklink() {
    m_InputFlags = 0;
}

CameraDecklink::~CameraDecklink() {
}

bool CameraDecklink::initializeCamera(IDeckLink *_deckLink) {
    m_PixelFormat = bmdFormat8BitYUV;

    if (_deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&m_DeckLinkInput) != S_OK)
        return false;

    m_Delegate = new DeckLinkCaptureDelegate();
    pthread_mutex_init(m_Delegate->sleepMutex, NULL);
    pthread_cond_init(m_Delegate->sleepCond, NULL);
    m_Result = m_DeckLinkInput->SetCallback(m_Delegate);

    if (m_Result != S_OK) {
        fprintf(stderr,
                "Setting callback failed - result = %08x\n",
                m_Result);

        return false;
    }

    return true;
}

bool CameraDecklink::startCapture() {
    m_Result = m_DeckLinkInput->EnableVideoInput(   m_SelectedDisplayMode,
                                                    m_PixelFormat,
                                                    m_InputFlags);
    if(m_Result != S_OK) {
        fprintf(stderr,
                "Failed to enable video input. Is another application using the card? - result = %08x\n",
                m_Result);

        return false;
    }

    m_Result = m_DeckLinkInput->StartStreams();
    if(m_Result != S_OK) {
        return false;
    }

    return true;
}

// Ilias from CapturePreview example
void CameraDecklink::stopCapture() {
    // Stop the capture
    m_DeckLinkInput->StopStreams();

    // Delete capture callback. This also releases.
    m_DeckLinkInput->SetCallback(NULL);

    m_DeckLinkInput->Release();
}

IplImage* CameraDecklink::captureLastFrame() {
    pthread_mutex_lock(m_Delegate->sleepMutex);
    pthread_cond_wait(m_Delegate->sleepCond, m_Delegate->sleepMutex);
    pthread_mutex_unlock(m_Delegate->sleepMutex);

    return m_Delegate->getLastImage();
}

cv::Mat CameraDecklink::captureLastCvMat(){
    IplImage* img = captureLastFrame();
    cv::Mat mat = img;
    return mat;
}

QString CameraDecklink::toQString(CFStringRef str) {
    if (!str)
        return QString();

    CFIndex length = CFStringGetLength(str);
    if (length == 0)
        return QString();

    QString string(length, Qt::Uninitialized);
    CFStringGetCharacters(str, CFRangeMake(0, length), reinterpret_cast<UniChar *>
        (const_cast<QChar *>(string.unicode())));
    return string;
}

bool CameraDecklink::fetchSelectedDisplayMode(int modeIndex) {
    IDeckLinkDisplayModeIterator*   displayModeIterator;
    int                             displayModeCount;
    bool                            foundDisplayMode;

    // Obtain an IDeckLinkDisplayModeIterator
    // to enumerate the display modes supported on output
    m_Result = m_DeckLinkInput->GetDisplayModeIterator(&displayModeIterator);
    if (m_Result != S_OK) {
        fprintf(stderr, "Could not get display mode iterator - result = %08x\n", m_Result);
        return false;
    }

    displayModeCount = 0;
    while (displayModeIterator->Next(&m_DisplayMode) == S_OK) {

        // Getting the displaymode name and printing it:
        CFStringRef displayModeName;
        m_DisplayMode->GetName(&displayModeName);
        QString     dmName      = toQString(displayModeName);
        QByteArray  byteArray   = dmName.toUtf8();
        const char* cString     = byteArray.constData();
        //QString qString2 = QString::fromUtf8(cString); // example of converting back to QString.
        fprintf(stderr, "Display mode name: %s \n", cString);

        if (modeIndex == displayModeCount) {
            BMDDisplayModeSupport result;

            foundDisplayMode = true;

            m_SelectedDisplayMode = m_DisplayMode->GetDisplayMode();

            m_DeckLinkInput->DoesSupportVideoMode(  m_SelectedDisplayMode,
                                                    m_PixelFormat,
                                                    bmdVideoInputFlagDefault,
                                                    &result,
                                                    NULL);

            if (result == bmdDisplayModeNotSupported) {
                fprintf(stderr, "The display mode %s is not supported with the selected pixel format\n", cString);
                return false;
            }

            break;
        }
        displayModeCount++;
        m_DisplayMode->Release();
    }

    if (!foundDisplayMode) {
        fprintf(stderr, "Invalid mode %d specified\n", modeIndex);
        return false;
    }

    return true;
}

// --- Delegate:

DeckLinkCaptureDelegate::DeckLinkCaptureDelegate() : m_refCount(0) {
    pthread_mutex_init(&m_mutex, NULL);

    lastImage = 0;
    frameCount = 0;
    stopped = false;
    sleepMutex = new pthread_mutex_t();
    sleepCond = new pthread_cond_t();

    g_timecodeFormat = 0;

    height = 720;
    width = 1280;
}

DeckLinkCaptureDelegate::~DeckLinkCaptureDelegate() {
    pthread_mutex_destroy(&m_mutex);

    // Ilias: I added these becuase probably they need to be here, no?
    pthread_mutex_destroy(sleepMutex);
    pthread_cond_destroy(sleepCond);
}

ULONG DeckLinkCaptureDelegate::AddRef(void) {
    pthread_mutex_lock(&m_mutex);
    m_refCount++;
    pthread_mutex_unlock(&m_mutex);

    return (ULONG)m_refCount;
}

ULONG DeckLinkCaptureDelegate::Release(void) {
    pthread_mutex_lock(&m_mutex);
    m_refCount--;
    pthread_mutex_unlock(&m_mutex);

    if (m_refCount == 0) {
        delete this;
        return 0;
    }

    return (ULONG)m_refCount;
}

HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame,
                                                        IDeckLinkAudioInputPacket* audioFrame) {
    IDeckLinkVideoFrame*	                rightEyeFrame = NULL;
    IDeckLinkVideoFrame3DExtensions*        threeDExtensions = NULL;
    unsigned char * pData;
    void*			audioFrameBytes;

    // Handle Video Frame
    if(videoFrame && !stopped) {
//        // If 3D mode is enabled we retreive the 3D extensions interface which gives.
//        // us access to the right eye frame by calling GetFrameForRightEye() .
//        if ( (videoFrame->QueryInterface(IID_IDeckLinkVideoFrame3DExtensions, (void **) &threeDExtensions) != S_OK) ||
//             (threeDExtensions->GetFrameForRightEye(&rightEyeFrame) != S_OK))
//        {
//            rightEyeFrame = NULL;
//        }

        // Ilias:
        rightEyeFrame = NULL;

        if (threeDExtensions)
            threeDExtensions->Release();

        if (videoFrame->GetFlags() & bmdFrameHasNoInputSource) {
    //        fprintf(stderr, "Frame received (#%lu) - No input signal detected\n", frameCount);
        }
        else {
            const char *timecodeString = NULL;
            if (g_timecodeFormat != 0) {
                IDeckLinkTimecode *timecode;
                if (videoFrame->GetTimecode(g_timecodeFormat, &timecode) == S_OK) {
                 // Ilias Commented.
                 //   timecode->GetString(&timecodeString);
                }
            }

//            fprintf(stderr, "Frame received (#%lu) [%s] - %s - Size: %li bytes\n",
//                    frameCount,
//                    timecodeString != NULL ? timecodeString : "No timecode",
//                    rightEyeFrame != NULL ? "Valid Frame (3D left/right)" : "Valid Frame",
//                    videoFrame->GetRowBytes() * videoFrame->GetHeight());

            if (timecodeString)
                free((void*)timecodeString);

            videoFrame->GetBytes(&frameBytes);
        }

        if (rightEyeFrame)
            rightEyeFrame->Release();

        frameCount++;

        pthread_cond_signal(sleepCond); // kill after one frame
    }

    return S_OK;
}

IplImage* DeckLinkCaptureDelegate::getLastImage() {
    if(!lastImage)
        lastImage = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);

    convertFrameToOpenCV(frameBytes, lastImage);
    return lastImage;
}

void DeckLinkCaptureDelegate::convertFrameToOpenCV(void* frameBytes, IplImage * m_RGB) {
    if(!m_RGB)
        m_RGB = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);

    unsigned char* pData = (unsigned char *) frameBytes;

    for(int i = 0, j=0; i < width * height * 3; i+=6, j+=4) {
        unsigned char u = pData[j];
        unsigned char y = pData[j+1];
        unsigned char v = pData[j+2];

        //fprintf(stderr, "%d\n", v);
        m_RGB->imageData[i+2] = 1.0*y + 8 + 1.402*(v-128);               // r
        m_RGB->imageData[i+1] = 1.0*y - 0.34413*(u-128) - 0.71414*(v-128);   // g
        m_RGB->imageData[i] = 1.0*y + 1.772*(u-128) + 0;                            // b

        y = pData[j+3];
        m_RGB->imageData[i+5] = 1.0*y + 8 + 1.402*(v-128);               // r
        m_RGB->imageData[i+4] = 1.0*y - 0.34413*(u-128) - 0.71414*(v-128);   // g
        m_RGB->imageData[i+3] = 1.0*y + 1.772*(u-128) + 0;
    }
}

HRESULT DeckLinkCaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents events,
                                                         IDeckLinkDisplayMode *mode,
                                                         BMDDetectedVideoInputFormatFlags) {
    return S_OK;
}
